/*
 * Copyright 2007 - The JDPF Project Team (http://www.jdpf.org) 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package org.jdpf.plugins.file.gen.intervalms.intervalmsreader;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.measure.Measure;
import javax.measure.unit.Unit;

import org.jdpf.core.kernel.elements.Token;
import org.jdpf.core.plugins.blocks.AbstractGeneratorPlugin;
import org.jdpf.core.plugins.exceptions.PluginException;
import org.jdpf.core.plugins.exceptions.PluginExceptionsEnum;
import org.jdpf.plugins.datatypes.basic.MeasureAdapter;
import org.jdpf.plugins.datatypes.basic.MeasurePrecision;
import org.jdpf.plugins.datatypes.basic.MeasureSeries;
import org.jdpf.plugins.datatypes.temporal.TimestampedMeasureAdapter;

/**
 * This plugin is a generator that reads integer values from a csv file, stores
 * them into a IntegerSeries data type. This series is finally inclosed to a
 * token and added to all output arc places.
 * 
 * @author Paolo Ciccarese
 * @version 0.1
 */

public class DelimitedFileIntevalMSReader extends AbstractGeneratorPlugin {
    
	/*
	 * Parameters list. We prefer to define the plugin parameters with an 
	 * underscore. For every plugin parameter it is necessary to implement 
	 * a 'set' method that is accepting a String variable. The framework,
	 * through reflection, will set the parameters at runtime. 
	 */
    private URI _inputFile; 									// Mandatory 
    private MeasurePrecision _dataFormat; 						// Mandatory
    
    /**
     * <p>The line format specifies the data to read and their order. 
     * It can include three elements:</p>
     * <ul>
     * 		<li>d - data</li>
     * 		<li>i - interval</li>
     * 		<li>u - unit of measure</li>
     * </ul>
     * <p>The default is a file with data and timestamp (without unit 
     * of measure).</p>
     */
    private String _lineFormat = "di";							// Optional
    private Unit _unitOfMeasure = Unit.ONE;						// Optional
    private String _timestampFormat = "yyyy-MM-dd HH:mm:ss"; 	// Optional
    private String _tokensSeparator = "\t"; 					// Optional
    
    /**
     * @param fileName	The file to read
     */
	public void setInputFile(URI fileName) {
		_inputFile = fileName;
	}

	/**
	 * If the file separator is defined as 'tab'
	 * we substitute the right character.
	 * @param separator	The file separator
	 */
	public void setTokensSeparator(String separator) {
		if (separator.equalsIgnoreCase("tab"))
			separator = Character.toString('\u0009');
		_tokensSeparator = separator;
	}

	public void setLineFormat(String lineFormat) {
		_lineFormat = lineFormat;
	}
	
	public void setUnitOfMeasure(String unitOfMeasure) {
		_unitOfMeasure = Unit.valueOf(unitOfMeasure.subSequence(0, unitOfMeasure.length()));
	}
	
	/**
	 * The timestamp format
	 * @param timestamp_format
	 */
	public void setTimestampFormat(String timestampFormat) {
		if (timestampFormat != null) _timestampFormat = timestampFormat;
	}
    
	/**
	 * The data format in between: {int, long, float, double, string}
	 * @param dataFormat	
	 * @throws PluginException
	 */
    public void setDataFormat(String dataFormat) throws PluginException {
    	_dataFormat = MeasurePrecision.getDataFormat(dataFormat);
	}
      
	/*
	 * Standard plugin constructor: every plugin has to implement the 
	 * constructor in this very same format.
	 */
    public DelimitedFileIntevalMSReader(String transitionId, String transitionName) {
		super(transitionId, transitionName);
	}	
    
    @SuppressWarnings("unchecked")
	public Token process(List<Token> list) throws PluginException{
    	MeasureSeries iSeries = readSeriesFromFile();
    	return setOutputToken(iSeries);
    }
	
    /**
     * @param fileName
     * @return
     * @throws PluginException 
     */
    private MeasureSeries readSeriesFromFile() throws PluginException {
    	
    	// Elements' position detection
    	int dataPosition = _lineFormat.indexOf('d');
    	int timePosition = _lineFormat.indexOf('i');
    	int unitPosition = _lineFormat.indexOf('u');
    	
    	// Timestamp format.
    	SimpleDateFormat sdf = null;
    	if (timePosition!=-1 && _timestampFormat != null) {
    		sdf = new SimpleDateFormat(_timestampFormat);
    	}
    	
    	MeasureSeries outputSeries = new MeasureSeries();
    /*
		try {
			BufferedReader br = null;
			String[] tokens = null;
			
			long fileLength = 0;
			String schema=_inputFile.getScheme();
			if(schema.equalsIgnoreCase("file")){
				FileReader f2 = new FileReader(new File (_inputFile));
				fileLength = (new File(_inputFile)).length();
				br = new BufferedReader(f2);
			}else if(schema.equalsIgnoreCase("http")){
				InputStream stream = _inputFile.toURL().openStream();
				br = new BufferedReader(new InputStreamReader(stream));
			}else
				throw new PluginException(PluginExceptionsEnum.IllegalParameter,this.getPluginId(),
						"Protocol '"+schema+"' not allowed.");			
			int processedBytes = 0;
			int lineCounter = 1;
			
			this.logMainPoint(this, this.getPluginId(), this.getId(), "Reading  started.");
			
			String readLine = null;
			MeasureAdapter value = null;
			Date timestamp = null;
			
			while (start && (readLine = br.readLine()) != null) {
				this.log(this, this.getPluginId(), this.getId(), "Line '" + lineCounter + "' in: " + readLine);
				
				// Updating progress percentage
				processedBytes+=readLine.length()+2;
				percentage = Math.min(100, ((double)processedBytes / fileLength) * 100);
				
				tokens = readLine.split(_tokensSeparator); 
				
				// TODO Checks tokens against line format
				if (timePosition == -1) {
					value = getDataValue(tokens[dataPosition], _unitOfMeasure, lineCounter);
					outputSeries.addElement(value);
				} else if (timePosition	 != -1) {
					timestamp = getTimestampValue(tokens[timePosition], sdf, lineCounter);
					value = getTimestampedDataValue(tokens[dataPosition], _unitOfMeasure, lineCounter, timestamp);
					outputSeries.addElement(value);
				}
				
				this.log(this, this.getPluginId(), this.getId(), "Parsed line '" + lineCounter + "' value in: " + value.getStringValue() + " unit: " + value.getMeasure().getUnit().toString());
				
				lineCounter++;
			}
			
			this.logMainPoint(this, this.getPluginId(), this.getId(), "Reading completed!");
			br.close();
		} catch (IOException e) {
			this.error(this, this.getPluginId(), this.getId(), "The input file " + _inputFile + " does not exist: "
					+ e.getMessage());
			throw new PluginException(PluginExceptionsEnum.ExecutionError, this.getPluginId(), "The input file " + _inputFile + " does not exist: "
					+ e.getMessage());
		}
		
		*/

		return outputSeries;
	}
    
    private MeasureAdapter getDataValue(String value, Unit unit, int line) throws PluginException {
		try {
			if(_dataFormat==MeasurePrecision.INT) 
				return new MeasureAdapter(Measure.valueOf(Integer.parseInt(value), unit));
			else if(_dataFormat==MeasurePrecision.LONG) 
				return new MeasureAdapter(Measure.valueOf(Long.parseLong(value), unit));
			else if(_dataFormat==MeasurePrecision.FLOAT) 
				return new MeasureAdapter(Measure.valueOf(Float.parseFloat(value), unit));
			else if(_dataFormat==MeasurePrecision.DOUBLE) 
				return new MeasureAdapter(Measure.valueOf(Double.parseDouble(value), unit));
			else {
				this.error(this, this.getPluginId(), this.getId(), "Type '" + _dataFormat + "' not recognized.");
				throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), "Type '" + _dataFormat + "' not recognized.");
			}
		} catch (NumberFormatException e) {
			this.error(this, this.getPluginId(), this.getId(), "Value '" + value + "' in line '" + line + "' is not of type: " + _dataFormat);
			throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), "Value '" + value + "' in line '" + line + "' is not of type: " + _dataFormat);
		}    	
    }
    
    private TimestampedMeasureAdapter getTimestampedDataValue(String value, Unit unit, int line, Date time) throws PluginException {
		try {
			if(_dataFormat==MeasurePrecision.INT) 
				return new TimestampedMeasureAdapter(Measure.valueOf(Integer.parseInt(value), unit), time);
			else if(_dataFormat==MeasurePrecision.LONG) 
				return new TimestampedMeasureAdapter(Measure.valueOf(Long.parseLong(value), unit), time);
			else if(_dataFormat==MeasurePrecision.FLOAT) 
				return new TimestampedMeasureAdapter(Measure.valueOf(Float.parseFloat(value), unit), time);
			else if(_dataFormat==MeasurePrecision.DOUBLE) 
				return new TimestampedMeasureAdapter(Measure.valueOf(Double.parseDouble(value), unit), time);
			else {
				this.error(this, this.getPluginId(), this.getId(), "Type '" + _dataFormat + "' not recognized.");
				throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), "Type '" + _dataFormat + "' not recognized.");
			}
		} catch (NumberFormatException e) {
			this.error(this, this.getPluginId(), this.getId(), "Value '" + value + "' in line '" + line + "' is not of type: " + _dataFormat);
			throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), "Value '" + value + "' in line '" + line + "' is not of type: " + _dataFormat);
		}    	
    }
    
    public Date getTimestampValue(String timestamp, SimpleDateFormat sdf, int line) throws PluginException {
    	try {
			Date ts = sdf.parse(timestamp);
			return ts;
		} catch (ParseException e) {
			this.error(this, this.getPluginId(), this.getId(), "Timestamp format error for value '" + timestamp + "' in line '" + line + " " + e.getMessage());
			throw new PluginException(PluginExceptionsEnum.InputTypeNotRecognized, this.getPluginId(), 
					"Timestamp format error for value '" + timestamp + "' in line '" + line + " " + e.getMessage());

		}
    }


}
